package service

import (
	"bytes"
	"errors"
	"gin-gorm-oj/model"
	"gin-gorm-oj/utils"
	"io"
	"log"
	"net/http"
	"os/exec"
	"runtime"
	"strconv"
	"sync"
	"time"

	"github.com/gin-gonic/gin"
	"gorm.io/gorm"
)

// Login
// @Tags 公共方法
// @Summary 用户登录
// @Param username formData string false "用户名" //表单参数，必填
// @Param password formData string false "密码" //表单参数，必填
// @Success 200 {string} json "{"code":"200","data":""}"
// @Router /login [post]
func Login(c *gin.Context) {
	// 从表单中获取用户名和密码
	username := c.PostForm("username")
	password := c.PostForm("password")

	if username == "" || password == "" {
		c.JSON(http.StatusOK, gin.H{
			"code": -1,
			"msg":  "必填信息为空",
		})
		return
	}

	// 对密码进行 MD5 哈希处理
	password = utils.GetMd5(password)

	// 创建一个 User 类型的指针，用于存储查询结果
	data := new(model.User)

	// 查询数据库，验证用户名和密码
	err := utils.DB.Where("name = ? AND password = ?", username, password).First(&data).Error
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			c.JSON(http.StatusOK, gin.H{
				"code": -1,
				"msg":  "用户名或密码错误",
			})
			return
		}
		c.JSON(http.StatusOK, gin.H{
			"code": -1,
			"msg":  "Get User Error:" + err.Error(),
		})
		return
	}

	// 生成 JWT 令牌
	token, err := utils.GenerateToken(data.ID, data.Name, data.IsAdmin)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"code": -1,
			"msg":  "GenerateToken Error:" + err.Error(),
		})
		return
	}

	// 返回成功响应，包含令牌和用户是否为管理员的标识
	/*当用户成功注册或登录后，系统会生成一个 token 并将其发送给用户。
	用户在后续的请求中可以将这个 token 附加到请求头中，
	服务器可以通过验证 token 来识别用户的身份，判断用户是否已登录。*/
	c.JSON(http.StatusOK, gin.H{
		"code": 200,
		"data": map[string]interface{}{
			"token":    token,
			"is_admin": data.IsAdmin,
		},
	})
}

// Register
// @Tags 公共方法
// @Summary 用户注册
// @Param mail formData string true "邮箱" // 表单参数，必填
// @Param code formData string true "验证码" // 表单参数，必填
// @Param name formData string true "用户名" // 表单参数，必填
// @Param password formData string true "密码" // 表单参数，必填
// @Param phone formData string false "手机号" // 表单参数，选填
// @Success 200 {string} json "{"code":"200","data":""}"
// @Router /register [post]
func Register(c *gin.Context) {
	// 获取表单参数
	mail := c.PostForm("mail")
	userCode := c.PostForm("code")
	name := c.PostForm("name")
	password := c.PostForm("password")
	phone := c.PostForm("phone")

	// 检查必填参数是否为空，如果有为空的字段则返回错误提示
	if mail == "" || userCode == "" || name == "" || password == "" {
		c.JSON(http.StatusOK, gin.H{
			"code": -1,
			"msg":  "参数不正确",
		})
		return
	}

	// 验证验证码：通过邮箱获取 Redis 中存储的验证码
	sysCode, err := utils.RDB.Get(c, mail).Result()
	if err != nil {
		log.Printf("Get Code Error:%v \n", err)
		c.JSON(http.StatusOK, gin.H{
			"code": -1,
			"msg":  "验证码不正确，请重新获取验证码",
		})
		return
	}

	// 验证用户输入的验证码是否与Redis中的验证码匹配
	if sysCode != userCode {
		c.JSON(http.StatusOK, gin.H{
			"code": -1,
			"msg":  "验证码不正确",
		})
		return
	}

	// 检查邮箱是否已经注册
	var cnt int64
	err = utils.DB.Where("mail = ?", mail).Model(new(model.User)).Count(&cnt).Error
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"code": -1,
			"msg":  "Get User Error:" + err.Error(),
		})
		return
	}
	// 如果邮箱已经注册，返回错误信息
	if cnt > 0 {
		c.JSON(http.StatusOK, gin.H{
			"code": -1,
			"msg":  "该邮箱已被注册",
		})
		return
	}

	// 将用户数据插入到数据库中
	data := &model.User{
		Name:      name,
		Password:  utils.GetMd5(password), // 对用户密码进行 MD5 加密后存储
		Phone:     phone,
		Mail:      mail,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
		IsAdmin:   1,
	}

	// 将用户数据插入数据库，保存新用户信息
	err = utils.DB.Create(data).Error
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"code": -1,
			"msg":  "Create User Error:" + err.Error(),
		})
		return
	}

	// 生成 JWT token（令牌）
	token, err := utils.GenerateToken(data.ID, name, data.IsAdmin)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"code": -1,
			"msg":  "Generate Token Error:" + err.Error(),
		})
		return
	}

	// 返回注册成功的响应，包含生成的令牌
	/*当用户成功注册或登录后，系统会生成一个 token 并将其发送给用户。
	用户在后续的请求中可以将这个 token 附加到请求头中，
	服务器可以通过验证 token 来识别用户的身份，判断用户是否已登录。*/
	c.JSON(http.StatusOK, gin.H{
		"code": 200,
		"data": map[string]interface{}{
			"token": token,
		},
	})
}

// SendCode
// @Tags 公共方法
// @Summary 发送验证码
// @Param mail formData string true "邮箱"  // 表单参数，必填
// @Success 200 {string} json "{"code":"200","data":""}"
// @Router /send-code [post]
func SendCode(c *gin.Context) {
	// 从表单中获取 email 参数
	email := c.PostForm("mail")

	if email == "" {
		c.JSON(http.StatusOK, gin.H{
			"code": -1,
			"msg":  "参数不正确",
		})
		return
	}

	// 生成一个随机的验证码
	code := utils.GetRand()

	// 将生成的验证码存储在 Redis 中
	err := utils.RDB.Set(c, email, code, time.Second*300).Err()
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"code": -1,
			"msg":  "存储验证码到 Redis 失败:" + err.Error(),
		})
		return
	}

	// 向指定的 email 地址发送验证码
	err = utils.SendCode(email, code)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"code": -1,
			"msg":  "Send Code Error:" + err.Error(),
		})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"code": 200,
		"msg":  "验证码发送成功",
	})
}

// Submit
// @Tags 用户私有方法
// @Summary 代码提交
// @Param authorization header string true "JWT_token"  // 请求头参数，必填
// @Param problem_id query int true "问题id"  // 查询参数，必填
// @Param code body string true "提交的代码"  // 请求体参数，必填
// @Success 200 {string} json "{"code":"200","data":""}"
// @Router /user/submit [post]
func Submit(c *gin.Context) {
	// 从查询参数中获取问题id
	problemId, _ := strconv.Atoi(c.Query("problem_id"))

	// 读取请求体中的代码
	code, err := io.ReadAll(c.Request.Body)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"code": -1,
			"msg":  "Read Code Error:" + err.Error(),
		})
		return
	}

	// 从中间件保存的上下文中获取用户声明
	u, _ := c.Get("user_claims")
	userClaim := u.(*utils.UserClaims)
	// 保存代码到指定路径
	path, err := utils.CodeSave(code, userClaim.ID, userClaim.Name)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"code": -1,
			"msg":  "Code Save Error:" + err.Error(),
		})
		return
	}

	// 创建一个新的提交记录
	submit := &model.Submit{
		ProblemID: int32(problemId),
		UserID:    userClaim.ID,
		CodePath:  path,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}

	// 获取题目相关的测试用例
	var testCases []model.TestCase
	if err := utils.DB.Where("problem_id = ?", problemId).
		Preload("Problem").Find(&testCases).Error; err != nil {
		c.JSON(http.StatusOK, gin.H{
			"code": -1,
			"msg":  "Get Test Cases Error:" + err.Error(),
		})
		return
	}
	// 获取题目信息
	problem := testCases[0].Problem

	// 创建用于判断代码结果的通道
	AC := make(chan int) // 答案正确的通道
	WA := make(chan int) // 答案错误的通道
	// 编译错误的通道，如果不使用协程，则要设置缓冲区，防止死锁
	CE := make(chan int, 1)
	RE := make(chan int)  // 运行错误的通道
	OOM := make(chan int) // 运行超内存的通道

	// 记录通过的测试用例数量
	passCount := 0
	var lock sync.Mutex // 保护共享变量的锁
	var msg string      // 提示信息

	// 检查编译错误
	buildCmd := exec.Command("go", "build", "-o", "/tmp", path)
	var compileErrBuf bytes.Buffer
	buildCmd.Stderr = &compileErrBuf
	// 编译代码，并捕获错误信息
	if err := buildCmd.Run(); err != nil {
		log.Println(err, compileErrBuf.String())
		msg = compileErrBuf.String()
		CE <- 1
	} else {
		//编译成功，遍历测试用例并为每个测试用例启动一个 goroutine
		StdinPipeErr := false
		for _, tc := range testCases {
			if StdinPipeErr {
				return
			}
			go func(testCase *model.TestCase) {
				// 创建命令对象
				cmd := exec.Command("go", "run", path)

				// 创建缓冲区来捕获程序的输出和错误输出
				var out, stderr bytes.Buffer
				cmd.Stderr = &stderr
				cmd.Stdout = &out

				// 创建标准输入管道（StdinPipe），用于将数据输入到程序中
				stdinPipe, err := cmd.StdinPipe()
				if err != nil {
					log.Fatalln(err)
					StdinPipeErr = true
					c.JSON(http.StatusOK, gin.H{
						"code": -1,
						"msg":  "Submit Error:" + err.Error(),
					})
					return
				}

				// 将输入内容加上换行符后写入 stdinPipe
				io.WriteString(stdinPipe, testCase.Input+"\n")
				stdinPipe.Close() // 关闭 stdinPipe，确保输入结束
				var mem1 runtime.MemStats
				runtime.ReadMemStats(&mem1) // 读取内存状态
				// 执行程序
				err = cmd.Run()
				if err != nil {
					// 运行错误
					msg = stderr.String()
					log.Println(err, msg)
					RE <- 1
					return
				}

				var mem2 runtime.MemStats
				runtime.ReadMemStats(&mem2) // 读取内存状态
				// 检查是否超过内存限制
				usedMemory := (mem2.Alloc - mem1.Alloc) / 1024 // 以 KB 为单位
				if usedMemory > uint64(problem.MaxMem*1024) {
					OOM <- 1
					return
				}

				// 答案错误
				if testCase.Output != out.String() {
					WA <- 1
					return
				}

				lock.Lock()
				passCount++
				// 答案正确
				if passCount == len(testCases) {
					AC <- 1
				}
				lock.Unlock()
			}(&tc)
		}
	}
	// 监控多个通道
	select {
	case <-AC:
		msg = "答案正确"
		submit.Status = 1
	case <-WA:
		msg = "答案错误"
		submit.Status = 2
	case <-CE: // 编译错误
		submit.Status = 3
	case <-RE: // 运行错误
		submit.Status = 4
	case <-time.After(time.Millisecond * time.Duration(problem.MaxRuntime)):
		msg = "运行超时"
		submit.Status = 5
	case <-OOM:
		msg = "运行超内存"
		submit.Status = 6
	}
	// 使用事务保存提交记录
	err = utils.DB.Transaction(func(tx *gorm.DB) error {
		err = tx.Create(submit).Error // 保存提交记录
		if err != nil {
			return errors.New("Submit Save Error:" + err.Error())
		}

		// 更新提交记录
		m := make(map[string]interface{})
		m["submit_num"] = gorm.Expr("submit_num + ?", 1) // 提交次数加一
		if submit.Status == 1 {
			m["pass_num"] = gorm.Expr("pass_num + ?", 1) // 正确答案次数加一
		}
		// 更新 user
		err = tx.Model(new(model.User)).Where("id = ?", userClaim.ID).Updates(m).Error
		if err != nil {
			return errors.New("User Modify Error:" + err.Error())
		}

		// 更新 problem
		err = tx.Model(new(model.Problem)).Where("id = ?", problemId).Updates(m).Error
		if err != nil {
			return errors.New("Problem Modify Error:" + err.Error())
		}
		return nil
	})

	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"code": -1,
			"msg":  "Submit Error:" + err.Error(),
		})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"code": 200,
		"data": map[string]interface{}{
			"status": submit.Status,
			"msg":    msg,
		},
	})
}
